package com.ruoyi.system.controlserver;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.system.domain.SrswDevices;
import com.ruoyi.system.mapper.SrswDevicesMapper;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author chenjy
 * @date 2024/12/3 15:03
 */
@Component
public class ControlServer {
    private static final Logger log = LoggerFactory.getLogger(ControlServer.class);

    private final Map<String, SmartInfo> channelMap = new HashMap<>();
    private final Map<String, String> instructResult = new HashMap<>();
    @Autowired
    private SrswDevicesMapper srswDevicesMapper;


    public static void main(String[] args) {
        new ControlServer().start();
    }

    @PostConstruct
    public void start() {
        String host = "0.0.0.0";
        int port = 9900;
        new Thread(() -> listen(host, port)).start();
    }


    private void listen(String host, int port) {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 100)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ChannelPipeline p = ch.pipeline();
                            p.addLast(new IdleStateHandler(0, 0, 30, TimeUnit.SECONDS){
                                @Override
                                protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) {
                                    // 30秒内无读无写，关闭连接
                                    IdleStateEvent event = (IdleStateEvent) evt;
                                    if (evt.state() == IdleState.ALL_IDLE) {
                                        // 发送自身状态
                                        ctx.close();
                                    }
                                }
                            });
                            ch.pipeline().addLast(new StringDecoder());
                            ch.pipeline().addLast(new StringEncoder());
                            p.addLast(new ChannelInboundHandlerAdapter() {
                                private InetSocketAddress address;
                                private String deviceNo;

                                @Override
                                public void channelActive(ChannelHandlerContext ctx) {
                                    final SocketAddress socketAddress = ctx.channel().remoteAddress();
                                    address = (InetSocketAddress) socketAddress;
                                    log.info("连接建立,{}", address);
//                                    final SmartInfo smartInfo = new SmartInfo(address.getHostName(), ctx.channel());
//                                    channelMap.put(address.getHostName(), smartInfo);
                                }

                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                    log.info("得到客户端 {} 发来的消息:{}", address, msg.getClass());
                                    if (msg instanceof String) {
                                        String info = (String) msg;
                                        log.info("客户端 {} 内容：{}", address, info);
                                        // 指令下发给智慧屏后，智慧屏给出的指令执行结果，eg:   answer--screen:on--success
                                        final String answer = "answer--";
                                        if (info.startsWith(answer)) {
                                            final String responseAnswer = info.substring(answer.length());
                                            final String[] array = responseAnswer.split("--");
                                            instructResult.put(array[0], array[1]);
                                            updateCacheState(deviceNo, info);
                                            return;
                                        }
                                        // 智慧屏定时给服务器发送自身状态，
                                        // eg:   notice--jsonObject(内容包含screenStatus,source,voice,opsStatus,muteStatus)
                                        final String notice = "notice--";
                                        if (info.startsWith(notice)) {
                                            final String responseNotice = info.substring(notice.length());
                                            final SocketAddress socketAddress = ctx.channel().remoteAddress();
                                            InetSocketAddress address = (InetSocketAddress) socketAddress;
                                            final SmartInfo smartInfo = new SmartInfo(address.getHostName(), ctx.channel());
                                            final JSONObject jsonObject = JSON.parseObject(responseNotice);
                                            smartInfo.setScreenStatus(jsonObject.getBoolean("screenStatus"));
                                            smartInfo.setSource(jsonObject.getString("source"));
                                            smartInfo.setVoice(jsonObject.getInteger("voice"));
                                            smartInfo.setOpsStatus(jsonObject.getBoolean("opsStatus"));
                                            smartInfo.setMuteStatus(jsonObject.getBoolean("muteStatus"));
                                            smartInfo.setDeviceNo(jsonObject.getString("deviceNo"));
                                            smartInfo.setDeviceName(jsonObject.getString("deviceName"));
                                            smartInfo.setSource(jsonObject.getString("source"));
                                            deviceNo=smartInfo.getDeviceNo();
                                            channelMap.put(smartInfo.getDeviceNo(), smartInfo);
                                            SrswDevices srswDevices = srswDevicesMapper.selectSrswDevicesByDeviceNo(jsonObject.getString("deviceNo"));
                                            if(srswDevices!=null){
                                                srswDevices.setUpTime(new Date());
                                                srswDevices.setDeviceName(jsonObject.getString("deviceName"));
                                                srswDevicesMapper.updateSrswDevices(srswDevices);
                                            }else{
                                                SrswDevices srswDevicesNew = new SrswDevices();
                                                srswDevicesNew.setDeviceNo(jsonObject.getString("deviceNo"));
                                                srswDevicesNew.setDeviceName(jsonObject.getString("deviceName"));
                                                srswDevicesNew.setUpTime(new Date());
                                                srswDevicesNew.setCreateTime(new Date());
                                                srswDevicesNew.setDeptId(100L);
                                                srswDevicesMapper.insertSrswDevices(srswDevicesNew);
                                            }
                                            return;
                                        }
                                        log.error("服务器收到 {} 发送的未定义的数据格式", address);
                                        return;
                                    }
                                    log.error("服务器收到 {} 发送的未预期的数据类型", address);
                                }

                                @Override
                                public void channelInactive(ChannelHandlerContext ctx) {
                                    log.info("客户端 {} 连接断开", address);
                                }

                                @Override
                                public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                                    log.info("服务器发生了异常或者客户端 {} 关闭了链接", address);
                                    super.exceptionCaught(ctx, cause);
                                }
                            });
                        }
                    });
            ChannelFuture f = b.bind(host, port).sync();
            log.info("端口{}开始监听", port);
            f.channel().closeFuture().sync();
            log.info("端口{}关闭，数据服务退出", port);
        } catch (Exception e) {
            log.error("异常发生，端口{}监听关闭，任务退出", port, e);
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException ex) {
                log.info("InterruptedException", ex);
            }
            this.listen(host, port);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    private void updateCacheState(String deviceNo, String info) {
        SmartInfo smartInfo = channelMap.get(deviceNo);
        if (smartInfo != null) {
            // answer--screen:on--success
            String[] split = info.split("--");
            if (split.length == 3) {
                String instruction = split[1];
                if (instruction.startsWith("screen")) {
                    smartInfo.setScreenStatus(!smartInfo.getScreenStatus());
                }
                if (instruction.startsWith("ops")) {
                    smartInfo.setOpsStatus(!smartInfo.getOpsStatus());
                }
                if (instruction.startsWith("mute")) {
                    smartInfo.setMuteStatus(!smartInfo.getMuteStatus());
                }
                if (instruction.startsWith("voice")) {
                    String[] voiceArray = instruction.split(":");
                    if (voiceArray.length == 2) {
                        smartInfo.setVoice(Integer.parseInt(voiceArray[1]));
                    }
                }
                if (instruction.startsWith("source")) {
                    String[] sourceArray = instruction.split(":");
                    if (sourceArray.length == 2) {
                        smartInfo.setSource(sourceArray[1]);
                    }
                }
            }
        }
    }

    public int instructResult(String instruct) {
        String s = instructResult.get(instruct);
        int count = 100;
        while (s == null) {
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count--;
            if (count < 0) {
                break;
            }
            s = instructResult.get(instruct);
        }
        instructResult.remove(instruct);
        if (s == null) {
            throw new ServiceException("10秒内未接收到智慧屏" + instruct + "指令执行结果");
        }
        String success = "success";
        if (success.equals(s)) {
            return 1;
        }
        throw new ServiceException("智慧屏指令执行失败：" + s);
    }

    public SmartInfo getChannelByDeviceNo(String deviceNo) {
        return this.channelMap.get(deviceNo);
    }

    public Map<String, SmartInfo> getChannelMap() {
        return channelMap;
    }
}
